import asyncio
import datetime
import json
import sys
import time
from collections import defaultdict
from datetime import timedelta
from json import JSONDecodeError

import aiohttp
import aioredis
import pymongo
import tornado
from phone import Phone
from tornado import gen

from baseHandler import Application, PayHandler, init_logger
from util.cmcc_10085 import cmcc_query
from util.ctcc2 import get_aliqr, ctcc_query

lock = asyncio.Lock()
RECHANGE_QUEUE = defaultdict(asyncio.Queue)

INTEVAL = 0.2
QUERY_TIME = 60 * 6


def get_ts():
    return str(time.time()).replace('.', '')[:13]



class CtccHandler(PayHandler):
    @gen.coroutine
    def post(self):
        logger.info(self.request.path)
        db = self.application.db
        exited = yield self.merchant_exit()
        if not exited:
            logger.info("商户不存在")
            return self.write(json.dumps({"message": "商户不存在", "ret": -1}))
        if not self.check_ip():
            return self.write(json.dumps({"message": "ip错误", "ret": -1}))
        if not self.check_sign():
            logger.info("签名验证失败")
            return self.write(json.dumps({"message": "签名验证失败", "ret": -1}))
        data = self.decrypt()
        if not data:
            return self.write({"message": "解密失败", "ret": -1})
        data = json.loads(data)
        logger.info(data)
        arguments = ["ordernumber", "amount", "tel_number"]
        for arg in arguments:
            if arg not in data:
                return self.write({"ret": -1, "message": "缺少%s" % arg})
        # prov = data.get("prov", "")
        konghao = yield self.application.redis.sismember("konghao", data['tel_number'])
        if konghao:
            logger.info("%s空号不可用", data['tel_number'])
            return self.write({"ret": -1, "message": "空号不可用"})

        logger.info("充值%s|%s|%s", data['ordernumber'], data['tel_number'], data['amount'])
        res = self.check_ordernumber(data)
        if res:
            return self.write(res)

        if data["amount"] not in ('10', '20', '30', '50', '100', '200', '300', '400', '500'):
            return self.write({"message": "不支持的金额", "ret": -1})

        p = Phone()
        prov_res = p.find(data['tel_number'])
        if prov_res:
            logger.info(prov_res)
            unused = yield self.application.redis.sismember("unused_ctcc_prov", prov_res.get("province"))
            if unused:
                logger.info("%s交费不可用", prov_res.get("province"))
                return self.write({"ret": -1, "message": "该省交易暂停"})
        else:
            logger.info('%s|省份查询失败', data['tel_number'])
            prov_res = {}

        if data["amount"] not in ('10', '20', '30', '50', '100', '200', '300', '400', '500'):
            return self.write({"message": "不支持的金额", "ret": -1})

        queue_name = "ctcc_%s_%s" % (self.merchantid, data["amount"])
        queue_size = yield self.application.redis.llen(queue_name)
        if not queue_size:
            queue_size = 0
        print(queue_size)
        if queue_size > 30:
            return self.write({"ret": -1, "message": "队列已满"})

        start_time = time.time()
        obj = {
            "tel_number": data["tel_number"],
            "ordernumber": data["ordernumber"],
            "time": start_time,
            "amount": data["amount"],
            # "porv": prov_res.get("province")
        }

        data['time'] = start_time
        data["orderdate"] = datetime.datetime.now().strftime('%Y%m%d')
        data["merchantid"] = self.merchantid
        data['tel_type'] = 'ctcc'
        data['orderstate'] = '2'
        data['prov'] = prov_res.get("province")
        try:
            yield db.rechange.insert_one(data)
            self.application.redis.lpush('ctcc_recharge', json.dumps({"ordernumber": data['ordernumber']}))
        except pymongo.errors.DuplicateKeyError:
            return self.write({"ret": -1, "message": "重复的订单号"})

        yield self.application.redis.lpush(queue_name, json.dumps(obj))

        res = {"orderstate": '2', "message": "订单提交成功", }
        return_obj = self.niti_data(res, {"MERCHANTID": self.merchantid})
        return_obj.update({"ret": 0, "message": "success"})
        self.write(return_obj)
        self.finish()


class WxHandler(PayHandler):
    @gen.coroutine
    def post(self):
        is_close = yield self.application.redis.get("is_ctcc_close", encoding='utf-8')
        if is_close == '1':
            logger.info("通道关闭")
            return self.write({"ret": -1, "message": "通道关闭"})
        db = self.application.db
        exited = yield self.merchant_exit()
        if not exited:
            logger.info("商户不存在")
            return self.write(json.dumps({"message": "商户不存在", "ret": -1}))
        if not self.check_ip():
            return self.write(json.dumps({"message": "ip错误", "ret": -1}))
        if not self.check_sign():
            logger.info("签名验证失败")
            return self.write(json.dumps({"message": "签名验证失败", "ret": -1}))
        data = self.decrypt()
        if not data:
            return self.write({"message": "解密失败", "ret": -1})
        data = json.loads(data)
        logger.info(data)
        arguments = ["payorderno", "amount"]
        for arg in arguments:
            if arg not in data:
                return self.write({"ret": -1, "message": "缺少%s" % arg})
        obj = yield db.rechange.find_one({"payorderno": data["payorderno"]})
        if obj:
            logger.info(obj)
            return self.write({"message": "订单号重复", "ret": -1})
        logger.info("支付订单%s|%s|%s", self.merchantid, data["payorderno"], data['amount'])
        supplier = self.merchant_info.get("supplier")
        if not supplier:
            return self.write({"message": "无有效供应商", "ret": -1})

        while True:
            try:
                pre_queue_name = "PRE_CTCC_%s_%s" % (supplier, data["amount"])
                result = yield self.application.redis.rpop(pre_queue_name, encoding='utf-8')
                if result:
                    result = json.loads(result)
                    ordernumber = result.get("ordernumber")
                    obj = yield db.rechange.find_one({"ordernumber": ordernumber})
                    if obj['orderstate'] == '1':
                        continue
                    if time.time() - result["time"] > 175:
                        logger.info("%s超过支付时间" % ordernumber)
                        continue
                    logger.info("预|%s|%s|%s", pre_queue_name, ordernumber, data["payorderno"])
                    user_data = result.get('userdata')
                else:
                    #####
                    queue_name = "ctcc_%s_%s" % (supplier, data["amount"])
                    logger.info(queue_name)
                    obj = yield self.application.redis.rpop(queue_name, encoding='utf-8')
                    if obj:
                        obj = json.loads(obj)
                    else:
                        return self.write({'ret': -1, 'message': "无有效订单"})
                    if time.time() - obj["time"] > 58:
                        if time.time() - obj['time'] < 60:
                            yield db.rechange.update_one({"ordernumber": obj["ordernumber"]},
                                                         {"$set": {
                                                             "orderstate": "1"
                                                         }})
                        continue
                    yield db.rechange.update_one({"ordernumber": obj["ordernumber"]},
                                                 {"$set": {
                                                     "orderstate": "8"
                                                 }})

                    ### 归属地查询
                    p = Phone()
                    prov_res = p.find(obj['tel_number'])
                    if prov_res:
                        logger.info(prov_res)
                        unused = yield self.application.redis.sismember("unused_ctcc_prov", prov_res.get("province"))
                        if unused:
                            logger.info("%s交费不可用", prov_res.get("province"))
                            continue

                    # 取单
                    is_login = False
                    for _ in range(5):
                        try:
                            # pay_channel = data.get("pay_channel")
                            # 提取账号
                            if not is_login:
                                user_data = yield self.application.redis.rpop("ctcc_user", encoding='utf-8')
                                if user_data:
                                    user_obj = yield db.cmcc_username.find_one({"username": user_data})
                                    logger.info("下单|%s|%s|%s", user_data, obj['ordernumber'], obj["tel_number"])
                                    cookies = user_obj.get('cookies')
                                    if not cookies:
                                        continue
                                    is_login = True
                                else:
                                    return self.write({'ret': -1, 'message': "没有账号"})

                            # 下单

                            future = get_aliqr(obj["tel_number"], obj['amount'], cookies, self.application.redis)
                            result = yield gen.with_timeout(timedelta(seconds=30), future)

                            # 失效单号剔除
                            if "登录失效" == result.get('message', ""):
                                is_login = False
                                yield db.cmcc_username.update_one({"username": user_data}, {"$set": {"cookies": None}})
                                continue
                            else:
                                break
                        except aiohttp.ClientConnectorError as e:
                            logger.info("%s|代理失效", obj["tel_number"])
                            logger.error(e, exc_info=True)
                            continue
                        except aiohttp.client_exceptions.ClientOSError as e:
                            logger.info("%s|代理失效2", obj["tel_number"])
                            logger.error(e, exc_info=True)
                            continue
                        except aiohttp.client_exceptions.ServerDisconnectedError as e:
                            logger.info("%s|代理失效3", obj["tel_number"])
                            logger.error(e, exc_info=True)
                            continue
                        except aiohttp.client_exceptions.ClientResponseError as e:
                            logger.info("%s|代理失效4", obj["tel_number"])
                            logger.error(e, exc_info=True)
                            continue
                        except JSONDecodeError as e:
                            logger.error(e, exc_info=True)
                            continue
                        except Exception as e:
                            logger.info("%s取单失败", obj["tel_number"])
                            logger.error(e, exc_info=True)
                            result = {"code": 2, 'message': "取单失败"}
                            break

                    if "登录失效" in result.get('messge', ""):
                        pass
                    else:
                        yield self.application.redis.lpush('ctcc_user', user_data)

                wx_url = result.get('qrcode')
                order_id = result.get('orderid')
                message = result.get("message")
                logger.info('%s|%s|%s', obj['ordernumber'], user_data, message)
                if wx_url:
                    orderstate = '8'
                else:
                    orderstate = '1'
                    yield db.rechange.update_one({"ordernumber": obj["ordernumber"]},
                                                 {"$set":  {"orderstate": orderstate, "message": message}}
                                                 )

                url = 'http://qr.nsdmkn.com/charge/v4.php?ordernumber=%s' % obj["ordernumber"]
                # url = wx_url
                res = {"orderstate": orderstate, "message": message, 'wx_url': url,
                       "orderid": order_id}
                if orderstate == '8':
                    yield db.rechange.update_one({"ordernumber": obj["ordernumber"]},
                                                 {"$set": {
                                                           'wx_url': wx_url,
                                                           "payorderno": data["payorderno"],
                                                           "merchantuid": self.merchantid,
                                                           "orderstate": orderstate,
                                                           "orderid": order_id,
                                                           "pay_backurl": data.get("backurl"),
                                                           "username": user_data,
                                                           }
                                                  })
                return_obj = self.niti_data(res, {"MERCHANTID": self.merchantid})
                return_obj.update({"ret": 0, "message": "success"})
                self.write(return_obj)
                self.finish()
                return
            except asyncio.QueueEmpty:
                return self.write({'ret': -1, 'message': "无有效订单"})


async def query(query_obj, db, redis):
    now = time.time()
    obj = await db.rechange.find_one(query_obj)
    extra = {}
    if not obj:
        orderstate = "1"
        message = "订单号不存在"
        return orderstate, message, extra
    orderstate = obj.get("orderstate", "2")

    if orderstate == "0" or orderstate == "1":
        message = obj.get("message", "")
        extra = {
            "tel_number": obj["tel_number"],
            "orderid": obj.get("orderid", ""),
        }
    else:
        async with lock:
            obj = await db.rechange.find_one(query_obj)
            query_time = obj.get("query_time") or obj["time"]
            if time.time() - query_time < 15:
                message = ""
                return orderstate, message, extra

            query_time = time.time()
            await db.rechange.update_one(query_obj, {"$set": {"query_time": query_time}})

        if obj.get('orderid'):
            if obj.get('orderid').startswith("3"):
                orderstate, message = await cmcc_query(obj['orderid'], obj.get("tel_number"), redis)
            else:
                username = obj.get("username")
                user_data = await db.cmcc_username.find_one({'username': username})
                cookies = user_data['cookies']
                orderstate, message = await ctcc_query(obj['orderid'], cookies, redis)
            if orderstate == '9':
                await redis.sadd("ctcc_9", obj['ordernumber'])
            if orderstate == '8':
                if query_time - obj['time'] > QUERY_TIME:
                    orderstate = '1'
                    # if not obj.get('orderid').startswith("3"):
                    #     await order_cancel(obj['orderid'], username, passwd, redis)
                else:
                    orderstate = obj.get("orderstate")
            extra = {
                "tel_number": obj["tel_number"],
                "orderid": obj.get("orderid", ""),
                # "url": obj.get("url", "")
            }
        else:
            if time.time() - obj['time'] < 60 and obj.get('orderstate', '2') == '2':
                orderstate = '2'
                message = obj.get("message", "取单中")
            elif time.time() - obj['time'] < 92 and obj.get('orderstate', '2') == '8':
                orderstate = '8'
                message = '获取二维码中'
            else:
                orderstate = '1'
                message = '未支付2'
            extra = {
                "tel_number": obj["tel_number"],
                "orderid": obj.get("orderid", ""),
                # "url": obj.get("url", "")
            }
        await db.rechange.update_one(query_obj, {
            "$set": {"orderstate": orderstate, "query_time": query_time, "message": message}})
    return orderstate, message, extra


class CtccQuery(PayHandler):
    @gen.coroutine
    def post(self):
        db = self.application.db
        exited = yield self.merchant_exit()
        if not exited:
            logger.info("商户不存在")
            return self.write(json.dumps({"message": "商户不存在", "ret": -1}))
        if not self.check_ip():
            return self.write(json.dumps({"message": "ip错误", "ret": -1}))
        if not self.check_sign():
            logger.info("签名验证失败")
            return self.write(json.dumps({"message": "签名验证失败", "ret": -1}))
        data = self.decrypt()
        if not data:
            return self.write({"message": "解密失败", "ret": -1})
        data = json.loads(data)

        if data.get("ordernumber"):
            query_obj = {"ordernumber": data["ordernumber"]}
        elif data.get("payorderno"):
            query_obj = {"payorderno": data["payorderno"]}

        else:
            return self.write({"message": "无效参数", "ret": -1})
        orderstate, message, extra = yield query(query_obj, db, self.application.redis)
        logger.info("查询%s|%s|%s|%s",  data.get("ordernumber"), data.get("payorderno"), orderstate, message)
        res = {"orderstate": orderstate, "message": message, **extra}
        return_obj = self.niti_data(res, {"MERCHANTID": self.merchantid})
        return_obj.update({"ret": 0, "message": "success"})
        self.write(return_obj)


class QueueHandler(PayHandler):
    @gen.coroutine
    def post(self):
        exited = yield self.merchant_exit()
        if not exited:
            logger.info("商户不存在")
            return self.write(json.dumps({"message": "商户不存在", "ret": -1}))
        if not self.check_ip():
            return self.write(json.dumps({"message": "ip错误", "ret": -1}))
        if not self.check_sign():
            logger.info("签名验证失败")
            return self.write(json.dumps({"message": "签名验证失败", "ret": -1}))
        res = {k: v.qsize() for k, v in RECHANGE_QUEUE.items()}
        logger.info(res)
        return_obj = self.niti_data(res, {"MERCHANTID": self.merchantid})
        return_obj.update({"ret": 0, "message": "success"})
        self.write(return_obj)


class SetTimeHandler(tornado.web.RequestHandler):
    @gen.coroutine
    def get(self, query_time):
        if int(query_time) < (6 * 60):
            return self.write("failed")
        QUERY_TIME = query_time
        return self.write('sucess')


if __name__ == "__main__":
    handlers = [
        (r"/pay/ct_recharge", CtccHandler),
        (r"/pay/ct_wxqrcode", WxHandler),
        (r"/pay/ct_query", CtccQuery),

    ]
    QUERY_TIME = 60 * 6
    application = Application(handlers, debug=False)
    port = sys.argv[1]
    application.listen(port, address="127.0.0.1")
    logger = init_logger("cmcc", "/app/log/cs_ctcc/ctcc%s.log" % port)
    logger.info("Run unionpay server ... http://127.0.0.1:%s", port)
    loop = asyncio.get_event_loop()
    application.redis = loop.run_until_complete(
        aioredis.create_redis(address=('localhost', 6379), db=0, loop=loop)
    )
    application.init_with_loop(loop)
    loop.run_forever()
